[line-bot-sdk-nodejs] メッセージ送信に失敗した時、エラーメッセージの詳細はどこにあるのか
line-bot-sdk-nodejsで .pushMessage()
などのリクエストに失敗した際、エラーの詳細はどこを見ればわかるのか一瞬迷ったので紹介します。
環境
- node 16.13.0
- typescript 4.7.4
- @line/bot-sdk 7.5.2
エラーが発生するサンプルコード
TypeScriptを使っていきます。「TypeScriptならSDKの型定義に従って書けば実行時エラーは起きないのではないか?」と思いがちですが、意外にエラーは発生します。
サンプルコードを提示します。
import { Client } from "@line/bot-sdk" const main = async () => { const client = new Client({ channelSecret: "XXXXXXXXXXXXXXXX", // チャネルシークレットをセット channelAccessToken: "XXXXXXXXXXXXXXXXXXXXXX", // チャネルアクセストークンをセット }) await client .pushMessage("Uxxxxxxxxxxxxxxxxxx", [ // 送信したいLINEユーザIDをセット { type: "text", text: "", }, ]) .catch((e: Error) => { console.error( JSON.stringify({ message: e.message, }) ) }) } main()
e.message
だけを出力しています。
上記実行するとエラーとなり、以下が出力されます。
{"message":"Request failed with status code 400"}
これだけだと「ステータスコードが400」という情報しかわからず、コード修正の道筋を立てることが難しいです。
原因調査のために追加でどのようなプロパティを出力すると良いのでしょうか?
.originalError.response.*
を出力するようにする
.originalError.response.data
を出力すると詳細がわかります。
他にも .originalError.response.status
.originalError.response.headers
など出力できます。
import { Client } from "@line/bot-sdk" const main = async () => { const client = new Client({ channelSecret: "XXXXXXXXXXXXXXXX", // チャネルシークレットをセット channelAccessToken: "XXXXXXXXXXXXXXXXXXXXXX", // チャネルアクセストークンをセット }) await client .pushMessage("Uxxxxxxxxxxxxxxxxxx", [ // 送信したいLINEユーザIDをセット { type: "text", text: "", }, ]) .catch((e: Error) => { if (e instanceof HTTPError) { console.error( JSON.stringify({ message: e.message, status: e.originalError.response.status, data: e.originalError.response.data, }) ) return } console.error( JSON.stringify({ message: e.message, }) ) }) } main()
出力は以下です。
{"message":"Request failed with status code 400","status":400,"data":{"message":"The request body has 1 error(s)","details":[{"message":"May not be empty","property":"messages[0].text"}]}}
引数の text
が空文字列 ""
であることが原因とわかりました。これはSDKの型定義による静的解析ではチェックされない点になります。
修正方針としては1文字以上の文字列を渡せば良いということになります。
調べた道筋
上記はline-bot-sdk-nodejsのREADMEなどには載っていなさそうです。
どのようにすれば e.originalError.response.*
の存在を知ることができそうでしょうか?備忘録兼ねまして紹介したいと思います。
ErrorオブジェクトをDumpして調べる
まず思いつくのがErrorオブジェクト全体をDumpして調べる方法です。
.catch((e: Error) => { + console.dir(e, { depth: null })
こちらの出力結果(割愛します)から、 e.originalError.response.data
に詳しいエラー理由が入っていると判断できます。
ソースコード+ドキュメントから調べる
line-bot-sdk-nodejsのソースコードからも追ってみます。
リクエスト失敗時は HTTPError
というエラークラスを投げているようなので、こちらの定義を見ます。
HTTPError
のコンストラクタに4つの引数を渡しており、引数名からこの中で詳細なエラーが得られそうなものは originalError
と当たりをつけます。
そして new HTTPError
している箇所をソース内検索で探します。
こちらより AxiosError
を originalError
としてセットしていることがわかります。line-bot-sdk-nodejsはAxiosに依存しており、ライブラリ利用者向けにもaxiosのエラーをそのまま扱っていそうだということがわかりました。
ということでaxiosのドキュメントを見ます。
https://github.com/axios/axios#handling-errors
console.log(error.response.data); console.log(error.response.status); console.log(error.response.headers);
のサンプルコードより、詳細(HTTPレスポンスボディ)を得るには .originalError.response.data
を取得すれば良さそうと推測でき、調査完了となります。
ちょっと予防線ですが、全体を丁寧に読み込めてはいないのでもし間違いがあればご指摘ください。
まとめ
line-bot-sdk-nodejsのエラーログ出力時には .originalError.response.data
を含めることで原因調査に役立つことを紹介しました。
そもそもErrorオブジェクト全体を出力するという手もありそうです。ただ実案件においては、
- 冗長なエラーログを避けたい
- ログ出力に機密情報が含まれることを避けたい
などの事情で、ログ出力対象を常に指定する意思決定もあるかと思います。
以上参考になれば幸いです。